Skip to content

Add UnifiedPush support with PushProvider abstraction#2

Open
sk7n4k3d wants to merge 26 commits into
mainfrom
unifiedpush
Open

Add UnifiedPush support with PushProvider abstraction#2
sk7n4k3d wants to merge 26 commits into
mainfrom
unifiedpush

Conversation

@sk7n4k3d
Copy link
Copy Markdown
Owner

Summary

Adds support for UnifiedPush as an alternative push notification provider, allowing users to receive notifications without relying on Google's FCM infrastructure. This is especially useful for the minimal/F-Droid flavor and privacy-conscious users.

Building on the work started by @lone-faerie in home-assistant#5261 and home-assistant#5301, this PR:

  • Introduces a generic PushProvider interface with priority-based selection via Dagger multibinding
  • Implements three concrete providers: FcmPushProvider (full flavor), UnifiedPushProvider, and WebSocketPushProvider (fallback)
  • Adds PushProviderManager to coordinate provider selection, registration, and server updates
  • Wires the new abstraction into LaunchActivity, LaunchPresenter, and SettingsPresenter
  • Adds a user-facing setting to choose a UnifiedPush distributor (e.g. ntfy, NextPush)
  • Includes 28 unit tests covering provider manager logic, interface contracts, and UP message parsing

Bug fixes on top of the original implementation

  • Fixed push_encrypt logic in IntegrationRepositoryImpl — the condition was inverted, causing UnifiedPush notifications to never be encrypted even when keys were provided
  • Fixed FcmPushProvider.isActive() — now correctly returns false when UnifiedPush is the active provider
  • Fixed resyncRegistration()selectAndRegister() is now called once before the server loop instead of once per server
  • Added error handling in UnifiedPushReceiver.onMessage() for malformed JSON payloads

Architecture

PushProvider (interface, common module)
├── FcmPushProvider      (priority 20, full flavor only)
├── UnifiedPushProvider  (priority 10, both flavors)
└── WebSocketPushProvider (priority 30, both flavors, fallback)

PushProviderManager (@Singleton, Dagger multibinding)
└── Selects best available provider, handles registration lifecycle

Related upstream: home-assistant#3174, home-assistant#5261, home-assistant#5301

Test plan

  • Unit tests for PushProviderManager (13 tests: selection, priority, switching, server updates)
  • Unit tests for PushProvider interface contracts (6 tests)
  • Unit tests for UnifiedPush message JSON parsing (9 tests)
  • Common module compiles and lint passes
  • Manual test: install with ntfy distributor → verify UnifiedPush is auto-selected
  • Manual test: settings → verify distributor picker shows available distributors
  • Manual test: verify notifications arrive via UnifiedPush endpoint
  • Manual test: verify FCM fallback works when no distributor is available
  • Manual test: verify minimal flavor works with UnifiedPush and WebSocket fallback

🤖 Generated with Claude Code

@sk7n4k3d sk7n4k3d force-pushed the unifiedpush branch 2 times, most recently from 7510038 to cebb60c Compare March 20, 2026 15:31
lone-faerie and others added 19 commits March 20, 2026 16:35
…ocket

Introduces a PushProvider abstraction layer that allows different push
notification backends to be plugged in via Dagger multibinding. This
addresses the review feedback requesting a scalable, generic interface
similar to the SensorManager pattern.

- PushProvider interface with priority-based selection
- PushProviderManager for runtime provider selection and registration
- FcmPushProvider (full flavor), UnifiedPushProvider, WebSocketPushProvider
- Dagger modules for full and minimal flavor bindings

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…sing

- PushProviderManagerTest: provider selection, priority ordering,
  registration, server updates, provider switching
- PushProviderTest: interface contract, default values, data class behavior
- UnifiedPushMessageParsingTest: JSON deserialization for notification
  payloads (actions, nested data, registration_info, confirm_id)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ttingsPresenter

Replace direct UnifiedPush/FCM token calls with PushProviderManager in:
- LaunchActivity: onOnboardingComplete uses selectAndRegister()
- LaunchPresenterImpl (full/minimal): resyncRegistration uses selectAndRegister()
- LaunchPresenterBase: accepts PushProviderManager as constructor param
- SettingsPresenterImpl: addServer uses selectAndRegister()

This completes the wiring of the generic PushProvider interface into the
existing codebase, removing direct dependencies on UnifiedPush connector
and getMessagingToken() from these classes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rror handling

- Fix pushEncrypt condition in IntegrationRepositoryImpl that was inverted:
  encryption was only applied for FCM URLs instead of UnifiedPush URLs.
  Now passes through the provider's encrypt flag directly.
- Fix FcmPushProvider.isActive() to return false when UnifiedPush is enabled,
  preventing incorrect provider detection.
- Fix resyncRegistration() in both flavors to call selectAndRegister() once
  before the server loop instead of once per server.
- Add try/catch around JSON parsing in UnifiedPushReceiver.onMessage() to
  prevent crashes on malformed payloads.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use MessagingToken() wrapper for pushToken in PushProviderManager
- Replace defaultServers with servers() suspend function
- Fix test mocks to use coEvery for suspend functions
- Replace Jackson with kotlinx.serialization in UnifiedPushMessageParsingTest
- Update gradle lockfiles for new dependencies
@sk7n4k3d sk7n4k3d force-pushed the unifiedpush branch 3 times, most recently from 8b2ffcc to b832294 Compare March 20, 2026 16:00
sk7n4k3d and others added 6 commits March 20, 2026 17:14
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The UnifiedPush connector depends on Tink which pulls in protobuf-java,
conflicting with protobuf-javalite used by Firebase/gRPC in the full
flavor. This exclusion was already applied to the minimal flavor.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adapt to upstream refactoring: MessagingTokenProvider, servers(),
kotlinx.serialization, and new SettingsPresenter interface.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add ExportedReceiver baseline entry for the UnifiedPush broadcast
receiver (must be exported for distributors to send messages) and
ObsoleteSdkInt for the automotive flavor SDK check.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 20, 2026

Test Results

0 tests   0 ✅  0s ⏱️
0 suites  0 💤
0 files    0 ❌

Results for commit 21e5e3e.

♻️ This comment has been updated with latest results.

Address reviewer feedback: remove priority-based auto-selection in favor
of a user-facing "Push provider" setting under Notifications. The user
can now switch between FCM, UnifiedPush distributors, and WebSocket at
any time without restarting the app.

- Remove `priority` field from PushProvider interface
- Replace hidden UnifiedPush-only preference with always-visible
  "Push provider" ListPreference showing all available providers
- Add IgnoreViolationRule for UnifiedPush connector StrictMode violations
- Fix UnifiedPush message parsing for nested JSON objects
- Auto-restart WebSocket push channel when switching providers
- Add fallback to empty token when FCM is unavailable
- Fix toast shown incorrectly when disabling UnifiedPush
- Update tests to reflect priority removal

Tested on Pixel 9 Pro Fold (Android 16, GrapheneOS):
- Switch WebSocket <-> UnifiedPush in both directions without restart
- Notifications received on both providers in foreground and background
- App killed: UnifiedPush still delivers via ntfy
- Auto-fallback to WebSocket when ntfy topic is deleted

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants